package roborally.program.command;

import exception.IllegalPositionException;
import roborally.factory.PhysicalAmountFactory;
import roborally.itemsonboard.Robot;
import roborally.itemsonboard.Wall;
import roborally.program.text.TextCommand;
import roborally.supporting.Orientation;
import roborally.supporting.PhysicalAmount;
import roborally.supporting.Position;
import roborally.supporting.Unit;
import roborally.usableitems.Usable;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;
//TODO documenteren van textmethodes van enum.
/**
 * A class representing a basic condition.
 * Such a condition consists of exactly one basic conditionCommand that can be executed immediately and is true or false.
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 1.0
 *
 */
public class BasicCondition extends Condition {
	
	/**
	 * Creates a new BasicCondition that can execute the given ConditionProcedure.
	 * 
	 * @param procedure The procedure describing this basic condition.
	 * @param arguments The extra arguments that might be needed.
	 * @effect Evokes the constructor of the superclass with the argument arguments.
	 * 		| super(arguments)
	 * @post The procedure of the new BasicCondition will be the given procedure.
	 * 		| (new this).getProcedure()==procedure()
	 * @throws IllegalArgumentException (repeated)
	 * 		When the arguments are not valid according to isPossibleArguments().
	 * 		| !isPossibleArguments(arguments)
	 * @throws IllegalArgumentException
	 * 		When the condition procedure is not valid according to canHaveAsProcedure().
	 * 		| !canHaveAsProcedure(procedure)
	 * @throws IllegalArgumentException
	 * 		When the arguments are not valid for the given condition procedure according to the method canHaveAsArguments()
	 * 		evoked after setting the procedure to the given procedure.
	 * 		| !(new this).canHaveAsArguments(arguments)
	 */
	public BasicCondition(ConditionProcedure procedure, Object... arguments) throws IllegalArgumentException {
		super(arguments); 
		if(!canHaveAsProcedure(procedure))
			throw new IllegalArgumentException("Illegal condition procedure");
		else
			this.procedure = procedure;
		if(!canHaveAsArguments(arguments))
			throw new IllegalArgumentException("Illegal argument for the basic condition " + this.getProcedure().toString());
	}

	/**
	 * Evaluates this condition for the given robot.
	 * 
	 * @param robot The robot for which this condition should be evaluated.
	 * @post The value returned by getReturnValue() of the condition leaving this method will be the value
	 * 			returned by the method evaluate of the procedure of this condition, with the arguments robot and this.getArguments().
	 * 		| (new this).getReturnValue() == this.getProcedure().evaluate(robot, this.getArguments())
	 * @throws IllegalArgumentException
	 * 		When the robot is not valid
	 * 		| !canHaveForExecution(robot)
	 */
	@Override
	public void execute(Robot robot) throws IllegalArgumentException {
		if(!canHaveForExecution(robot))
			throw new IllegalArgumentException();
		this.setReturnValue(this.getProcedure().evaluate(robot, this.getArguments()));
	}
	
	/**
	 * Returns the ConditionProcedure that represents this BasicCommand.
	 */
	@Basic @Raw @Immutable
	public ConditionProcedure getProcedure() {
		return procedure;
	}
	
	/**
	 * Checks whether this basic condition can have the given condition procedure as its procedure.
	 * 
	 * @param procedure The condition procedure for which it must be checked.
	 * @return False if the procedure is null.
	 * 		| if procedure == null
	 * 		|	then result == false
	 */
	public boolean canHaveAsProcedure(ConditionProcedure procedure) {
		return procedure != null;
	}
	
	/**
	 * Stores the procedure that determines how this basic condition should be evaluated.
	 */
	private final ConditionProcedure procedure;
	
	/**
	 * Checks whether this basic condition can have this arguments.
	 * 
	 * @return False if this has no valid executionProcedure and this can not have the given arguments according the superclass.
	 * 		| if !this.canHaveAsProcedure(this.getProcedure())
	 * 		|	then if !super.canHaveAsArguments(arguments)
	 * 		|			then result == false
	 * @return If the superclass cannot have the arguments, as defined at super.canHaveAsArguments(), then the result is false.
	 * 		| if !super.canHaveAsArguments(arguments)
	 * 		|	then result == false
	 * @return If the procedure of this condition cannot have the arguments according to 
	 * 			the procedure then the result is false.
	 * 		| if !this.getProcedure().canHaveAsArguments(arguments)
	 * 		|	then result == false
	 */
	@Override
	public boolean canHaveAsArguments(Object...arguments) {
		if(!this.canHaveAsProcedure(this.getProcedure()))
			return super.canHaveAsArguments(arguments);
		return super.canHaveAsArguments(arguments) && this.getProcedure().canHaveAsArguments(arguments);
	}
	
	/**
	 * Returns a String representation of this BasicCondition.
	 * 
	 * @return The result will be a String beginning with an opening bracket, followed by the name of the procedure,
	 * 			 followed by a String-representation of the arguments, and that ends by a closing bracket.
	 * 		| let write be a new String
	 * 		| if this.canHaveAsArguments(this.getArguments())
	 * 		|	then for each value in {this.getArguments()}
	 * 		|		write = write + " " + this.getProcedure().getArgument(value)
	 * 		| result == "(" + this.getProcedure().toString() + write + ")"
	 */
	@Override
	public String write() {
		String write = new String();
		if(this.canHaveAsArguments(this.getArguments()))
			for(Object value : this.getArguments())
				write = write + " " + this.getProcedure().getArgument(value);
		return "(" + this.getProcedure().toString() + write + ")";
	}

	/**
	 * An enum representing the basic conditions that stores the right procedures to determine the result of such a condition.
	 * Examples are energy-at-least, at-item, can-hit-robot and wall.
	 * 
	 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
	 * @version 1.0
	 *
	 */
	public enum ConditionProcedure implements TextCommand {
		ENERGY_AT_LEAST{

			/**
			 * Evaluates this energy-at-least condition that checks whether the energylevel of the robot is equal to or greater than the given value.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param arguments An array of object that are extra arguments that may be needed.
			 * @return The result returns whether the energyLevel (in Ws) of the robot is greater than or equal to the given amount (in Ws) or not.
			 * 		| let tempAmount be PhysicalAmountFactory.getInstance().newPhysicalAmount((Double)arguments[0], Unit.WATTSECOND)
			 * 		| result == (robot.getEnergyLevel().compareTo(tempAmount) >= 0)
			 * @throws IllegalArgumentException
			 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(arguments) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... arguments) throws IllegalArgumentException {
				if( !canHaveAsArguments(arguments) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("Energy-at-least should have an effective robot and one argument, a double");
				PhysicalAmount tempAmount = PhysicalAmountFactory.get().create((Double)arguments[0], Unit.WATTSECOND);
				int temp = robot.getEnergyLevel().compareTo(tempAmount);
				if(temp>=0)
					return true;
				return false;
			}
			
			/**
			 * Checks whether the arguments are legal for this basic condition procedure.
			 * 
			 * @param arguments The extra arguments that might be needed and that must be checked.
			 * @return True if and only if the arguments are not null and if the length of the arguments is 1 and if the argument is an instance of Double.
			 * 		| result == ( arguments != null && arguments.length == 1 && Double.class.isInstance(arguments[0] )
			 */
			@Override
			public boolean canHaveAsArguments(Object... arguments) {
				return arguments != null && arguments.length == 1 &&
					Double.class.isInstance(arguments[0]);
			}
			
			/**
			 * Returns an Object of wich the given string is the String-representation.
			 * 
			 * @param argument The String of which the corresponding object must be returned.
			 * @return If the argument contains a parsable double according to the Java API in the class Double
			 * 			then the result is the result of the method Double.parseDouble in the Java API.
			 * 		| if argument does contain a parsable double according to the Java API in the class Double
			 * 		|	then result == Double.parseDouble(argument)
			 */
			@Override
			public Object getValue(String argument) throws IllegalArgumentException {
				try {
					return Double.parseDouble(argument);
				} catch(NullPointerException exc) {
					return null;
				} catch(NumberFormatException exc) {
					return null;
				}
			}
			
			/**
			 * Returns a String representation of the given object if this procedure could contain the object.
			 * 
			 * @param value The value of which the corresponding argument (String) must be returned.
			 * @return The String representation of this value if valid.
			 * 		| if containsValue(value)
			 * 		|	then result == object.toString()
			 * @throws IllegalArgumentException
			 * 		When the value is not a valid argument
			 * 		| !containsValue(value)
			 */
			@Override 
			public String getArgument(Object value) { 
				if(containsValue(value))
					return value.toString();
				else
					throw new IllegalArgumentException();
			}

			@Override
			public boolean containsValue(Object value) {
				return Double.class.isInstance(value);
			}

			@Override
			public boolean containsArgument(String argument) {
				try {
					Double.parseDouble(argument);
					return true;
				} catch(NullPointerException exc) {
					return false;
				} catch(NumberFormatException exc) {
					return false;
				}
			}

		},
		AT_ITEM{

			/**
			 * Evaluates this at-item condition that checks whether a usable item is at the same position as the robot.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param arguments An array of object that are extra arguments that may be needed.
			 * @return False if the robot isn't on a board.
			 * 		| if robot.getBoard()==null
			 * 		|	then result == false
			 * @return The result is true if and only if the set of usable items at the position of the robot (see Board) is not empty.
			 * 		| result == !robot.getBoard().getItemsAtType(Usable.class, robot.getPosition()).isEmpty()
			 * @throws IllegalArgumentException
			 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(arguments) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... arguments) throws IllegalArgumentException {
				if( !canHaveAsArguments(arguments) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("At-item should have no arguments and an effective robot");
				if(robot.getBoard()==null)
					return false;
				else
					return !robot.getBoard().getItemsAtType(Usable.class, robot.getPosition()).isEmpty();
			}

			/**
			 * Checks whether the arguments are legal for this basic condition procedure.
			 * 
			 * @param arguments The extra arguments that might be needed and that must be checked.
			 * @return True if and only if or arguments is null or the length of arguments is 0.
			 * 		| result == ( arguments == null || arguments.length == 0 )
			 */
			@Override
			public boolean canHaveAsArguments(Object... arguments) {
				return (arguments == null || arguments.length == 0);
			}

			@Override
			public String getArgument(Object value)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsValue(Object value) {
				return false;
			}

			@Override
			public Object getValue(String argument)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsArgument(String argument) {
				return false;
			}
		},
		CAN_HIT_ROBOT{

			/**
			 * Evaluates this can-hit-robot condition whitch checks whether the robot can hit an other robot by shooting.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param arguments An array of object that are extra arguments that may be needed.
			 * @return If the robot isn't at a board, the result is false.
			 * 		| if robot.getBoard()==null
			 * 		|	then result == false
			 * @return True if and only if the robot could hit an other Robot by shooting as defined at Robot and Board.
			 *  	| let flag = true
			 * 		| for the first position in {robot.getPosition().move(robot.getOrientation), robot.getPosition().move(robot.getOrientation).move(robot.getOrientation(), ...} 
			 * 		|	for which ( robot.getBoard().canHaveAsPosition(position) && !robot.getBoard().getItemsAt(position).isEmpty() && flag ) is true
			 * 		|		then flag = false
			 * 		|			 if !(robot.getBoard().getItemsAtType(Robot.class, position).isEmpty())
			 * 		|				then result == true
			 * 		|			 else
			 * 		|				then result == false
			 * 		| result == false
			 * @throws IllegalArgumentException
			 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(arguments) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... arguments) throws IllegalArgumentException {
				if( !canHaveAsArguments(arguments) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("Can-hit-robot should have no arguments and an effective robot");
				if(robot.getBoard()==null)
					return false;
				Position position = robot.getPosition();
				try{
					while(true){
						position = position.move(robot.getOrientation());
						if(!robot.getBoard().canHaveAsPosition(position))
							break; // we have left the board at the down- or right side and thus we would hit nothing.
						else if(!robot.getBoard().getItemsAtType(Robot.class, position).isEmpty())
							return true;
						else if(!robot.getBoard().getItemsAt(position).isEmpty())
							return false;
							 // we would shoot something else (a wall or a battery or ...)
					}
				}
				catch(IllegalPositionException e1){return false;} // we have left the board at the up or left side and thus we would hit nothing.
				return false;
			}

			/**
			 * Checks whether the arguments are legal for this basic condition procedure.
			 * 
			 * @param arguments The extra arguments that might be needed and that must be checked.
			 * @return True if and only if or arguments is null or the length of arguments is 0.
			 * 		| result == ( arguments == null || arguments.length == 0 )
			 */
			@Override
			public boolean canHaveAsArguments(Object... arguments) {
				return (arguments == null || arguments.length == 0);
			}
			
			@Override
			public String getArgument(Object value)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsValue(Object value) {
				return false;
			}

			@Override
			public Object getValue(String argument)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsArgument(String argument) {
				return false;
			}

		},
		WALL{

			/**
			 * Evaluates this wall condition that checks whether there is a wall to the RIGHT of the robot.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param arguments An array of object that are extra arguments that may be needed.
			 * @return If the robot isn't at a board, the result is false.
			 * 		| if robot.getBoard()==null
			 * 		|	then result == false
			 * @return If the position to the right of the robot is not a valid position, the result is false.
			 * 		| if !Position.isValidX(robot.move(Orientation.RIGHT).getX()) || !Position.isValidY(robot.move(Orientation.RIGHT).getY())
			 * 		|	then result == false
			 * @return True if and only if the set of Wall objects located at the position to the right of the robot (see Board) is not empty.
			 * 		| if Position.isValidX(robot.move(Orientation.RIGHT).getX()) && Position.isValidY(robot.move(Orientation.RIGHT).getY())
			 * 		|	then result == !( robot.getBoard().getItemsAtType(Wall.class, robot.getPosition().move(Orientation.RIGHT)).isEmpty() )
			 * @throws IllegalArgumentException
			 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(arguments) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... arguments) throws IllegalArgumentException {
				if( !canHaveAsArguments(arguments) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("Wall should have no arguments and an effective robot");
				if(robot.getBoard()==null)
					return false;
				try{
					return !robot.getBoard().getItemsAtType(Wall.class, robot.getPosition().move(Orientation.RIGHT)).isEmpty();
				}
				catch(IllegalPositionException e1) {
					return false;
				} //the position to the right is an illegal position so there can't be a wall there.
			}

			/**
			 * Checks whether the arguments are legal for this basic condition procedure.
			 * 
			 * @param arguments The extra arguments that might be needed and that must be checked.
			 * @return True if and only if or arguments is null or the length of arguments is 0.
			 * 		| result == ( arguments == null || arguments.length == 0 )
			 */
			@Override
			public boolean canHaveAsArguments(Object... arguments) {
				return (arguments == null || arguments.length == 0);
			}
			
			@Override
			public String getArgument(Object value)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsValue(Object value) {
				return false;
			}

			@Override
			public Object getValue(String argument)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsArgument(String argument) {
				return false;
			}
		},
		TRUE{
			
			/**
			 * Evaluates this true condition that is just true.
			 * 
			 * @param robot The robot that should execute this program.
			 * @param arguments An array of object that are extra arguments that may be needed.
			 * @return True (always)
			 * 		| true
			 * @throws IllegalArgumentException
			 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments() and canHaveAsRobot()
			 * 		| !canHaveAsArguments(arguments) || !canHaveAsRobot(robot)
			 */
			@Override
			public boolean evaluate(Robot robot, Object... arguments)
					throws IllegalArgumentException {
				if( !canHaveAsArguments(arguments) || !canHaveAsRobot(robot))
					throw new IllegalArgumentException("True should have no arguments and an effective robot");
				return true;
			}

			/**
			 * Checks whether the arguments are legal for this basic condition procedure.
			 * 
			 * @param arguments The extra arguments that might be needed and that must be checked.
			 * @return True if and only if or arguments is null or the length of arguments is 0.
			 * 		| result == ( arguments == null || arguments.length == 0 )
			 */
			@Override
			public boolean canHaveAsArguments(Object... arguments) {
				return (arguments == null || arguments.length == 0);
			}

			@Override
			public String getArgument(Object value)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsValue(Object value) {
				return false;
			}

			@Override
			public Object getValue(String argument)
					throws IllegalArgumentException {
				throw new IllegalArgumentException();
			}

			@Override
			public boolean containsArgument(String argument) {
				return false;
			}
		};
		
		/**
		 * Evaluates this basic condition.
		 * 
		 * @param robot The robot that should execute this program.
		 * @param arguments An array of object that are extra arguments that may be needed.
		 * @throws IllegalArgumentException
		 * 		If the procedure can't have the given parameters as arguments as defined at canHaveAsArguments()
		 * 		| !canHaveAsArguments(arguments)
		 * @throws IllegalArgumentException
		 * 		If the procedure can't have the given robot as parameter.
		 * 		| !canHaveAsRobot(robot)
		 */
		public abstract boolean evaluate(Robot robot, Object... arguments) throws IllegalArgumentException;
				
		/**
		 * Checks whether this basicCondition can execute itself with the given robot as robot.
		 * 
		 * @param robot The robot for which this BasicCommand should be evaluated and that must be checked.
		 * @return If robot is null, the result is false.
		 * 		| if robot == null
		 * 		|	then result == false
		 * @return If robot is terminated, the result is false.
		 * 		| if robot.isTerminated()
		 * 		|	then result == false
		 */
		public boolean canHaveAsRobot(Robot robot) {
			return robot != null && !robot.isTerminated();
		}
		
		/**
		 * Controls if the given arguments are valid.
		 * 
		 * @param arguments The arguments to be controlled.
		 */
		public abstract boolean canHaveAsArguments(Object...arguments);
			
		/**
		 * Returns a StringPattern that determines a conditionProcedure.
		 */
		@Override
		public String getStringPattern() {
			return "^(\\()(\\s*)(" + this.toString() + ")(\\s*)(\\))";
		}
		
		/**
		 * Returns a textual representation of this executionProcedure.
		 * 
		 * @return Returns a String equal to the enum-literal but with lower cases and with - in stead of _
		 * 		| super.toString().toLowerCase().replaceAll("_", "-")
		 */
		@Override
		public String toString() {
			return super.toString().toLowerCase().replaceAll("_", "-");
		}
		
	}
	
}
